/*
 * Class representing a subscriber of a library
 * A subscriber knows his first and last name,
 * his born date and his id in the library
 * @author M.T. Segarra
 * @version 0.0.1
 */
package inf.subscribersManagement;


import inf.booksManagement.Book;
import inf.dvdsManagement.Dvd;
import inf.exceptions.*;
import inf.hibernateUtils.HibernateUtil;
import inf.loansManagement.Booking;
import inf.loansManagement.Loan;
import inf.main.Constraints;
import inf.mediasManagement.Media;
import inf.outils.StringCalendar;
import inf.specimensManagement.Specimen;


import java.util.*;

import javax.persistence.*;
import org.hibernate.Session;


/**
 * Entity implementation class for Entity: Subscriber
 */
@Entity
@Table(name="subscribers")
public class Subscriber {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name="id_subscriber", unique = true, nullable=false)
	private long id;

	private String firstName;

	private String lastName;

	private Calendar bornDate;

	private Calendar subscriptionDate;

	private int numberOfBookLent = 0;

	private int numberOfDvdLent = 0;

	private int numberOfMediaBooked = 0;

	@OneToMany(mappedBy = "subscriber", cascade = CascadeType.ALL)
	private List<Loan> loans;

	@OneToMany(mappedBy = "subscriber", cascade = CascadeType.ALL)
	private List<Booking> bookings;

	public long getId() {
		return id;
	}
	
	public void setId(long number) {
		this.id = number;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}


	public Calendar getBornDate() {
		return bornDate;
	}

	public void setBornDate(Calendar bornDate) {
		this.bornDate = bornDate;
	}

	public List<Loan> getLoans() {
		return loans;
	}

	public void setLoans(List<Loan> loans) {
		this.loans = loans;
	}

	public Calendar getSubscriptionDate() {
		return subscriptionDate;
	}

	public void setSubscriptionDate(Calendar subscriptionDate) {
		this.subscriptionDate = subscriptionDate;
	}

	public int getNumberOfBookLent() {
		return numberOfBookLent;
	}

	public void setNumberOfBookLent(int numberOfBookLoan) {
		this.numberOfBookLent = numberOfBookLoan;
	}

	public int getNumberOfDvdLent() {
		return numberOfDvdLent;
	}

	public void setNumberOfDvdLent(int numberOfDvdLent) {
		this.numberOfDvdLent = numberOfDvdLent;
	}

	public int getNumberOfMediaBooked() {
		return numberOfMediaBooked;
	}

	public void setNumberOfMediaBooked(int numberOfMediaBooked) {
		this.numberOfMediaBooked = numberOfMediaBooked;
	}

	public List<Booking> getBookings() {
		return bookings;
	}
	public void setBookings(List<Booking> bookings) {
		this.bookings = bookings;
	}

	public String getLastName() {
		return lastName;
	}

	/**
	 * Setter of the property <tt>lastname</tt>
	 * 
	 * @param lastname
	 *            The lastname to set.
	 * @uml.property name="lastName"
	 */
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public Subscriber() {}

	public Subscriber(String firstName, String lastName, Calendar bornDate, Calendar subscriptionDate) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.bornDate = bornDate;
		this.subscriptionDate = subscriptionDate;
		this.loans = new ArrayList<Loan>();
		this.bookings = new ArrayList<Booking>();
	}


	/**
	 * Decides if the subscriber object (parameter) is the same subscriber as
	 * this one Same first name, last name, and born date
	 * 
	 * @param subscriber object to be compared with this one
	 * 
	 * @return true if parameter object and this one are equal
	 * 
	 * @return false if parameter object is null or different from this one
	 */
	@Override
	public boolean equals(Object subscriber) {
		if (subscriber == null)
			return false;
		Subscriber s = (Subscriber) subscriber;

		if (s.id == id)
			return true;

		if ((s.firstName == null) || (s.lastName == null)
				|| (s.bornDate == null))
			return false;

		if ((this.firstName == null) || (this.lastName == null)
				|| (this.bornDate == null))
			return false;

		boolean res = (s.firstName.equals(firstName))
				&& (s.lastName.equals(lastName))
				&& (s.bornDate.equals(bornDate));

		return res;
	}

	public Loan lend(Specimen specimen) throws BadParametersException, TooManyLoansException, BookedSpecimenException {
		// Parametres
		if(specimen == null)
			throw new BadParametersException();
		
		// Quotas
		if(!this.canLend(specimen.getMedia()))
			throw new TooManyLoansException();
		
		// Cas des exemplaires reserve
		if(specimen.isBooked()){
			Booking booking = specimen.getBooking();
			// Cas 1 : l'emprunteur est la personne qui a reserve l'exemplaire
			if(this.equals(booking.getSubscriber())){
				this.endBooking(booking);
			} else {
				throw new BookedSpecimenException();
			}
		}		
		
		// Ok
		if (specimen.getMedia() instanceof Book)
			numberOfBookLent++;
		else if(specimen.getMedia() instanceof Dvd)
			numberOfDvdLent++;
				
		Loan loan = new Loan(specimen, this);
		
		specimen.setLoan(loan);
		specimen.getMedia().lendSpecimen();
				
		this.loans.add(loan);
		
		inf.mediasManagement.MediaDAO.save(specimen.getMedia());
		SubscriberDAO.save(this);
		
		return loan;
	}

	public void endOfLoan(Specimen specimen) throws BadParametersException, NotLentMediaException {
		if (specimen == null)
			throw new BadParametersException();
		
		Loan loan = specimen.getLoan();
		Media media = specimen.getMedia();
		
		if(loan == null)
			throw new NotLentMediaException();
		
		if (media instanceof Book)
			numberOfBookLent--;
		else if(media instanceof Dvd)
			numberOfDvdLent--;
		
		specimen.setLoan(null);
		media.returnSpecimen(specimen);
		
		loan.setReturned(true);
		
		inf.mediasManagement.MediaDAO.save(media);
		inf.loansManagement.LoanDAO.save(loan);
		SubscriberDAO.save(this);
		
		
	}

	public Booking book(Media media) throws BadParametersException, TooManyBooksException, AllSpecimensBooked {
		if(media == null)
			throw new BadParametersException();
		
		if(!this.canBook(media))
			throw new TooManyBooksException();
		
		if(!media.isBookable())
			throw new AllSpecimensBooked();
		
		// Ok
		Booking booking = new Booking(media, this);
		
		media.addBooking(booking);
		
		this.setNumberOfMediaBooked(this.getNumberOfMediaBooked() + 1);
		this.bookings.add(booking);
		
		inf.mediasManagement.MediaDAO.save(media);
		SubscriberDAO.save(this);
		
		return booking;
	}

	// End of a booking
	public void endBooking(Booking booking) throws BadParametersException{
		if(booking == null)
			throw new BadParametersException();
		
		this.setNumberOfMediaBooked(this.getNumberOfMediaBooked() - 1);
		this.bookings.remove(booking);
	}

	public List<String> history(){
		List<Loan> history = inf.loansManagement.LoanDAO.getByUserId(this.getId());
		List<String> retour = new ArrayList<String>();
		for(Loan loan : history){
			retour.add(loan.toString());
		}
		return retour;
	}

	/*
	 * Returns a description of the subscriber
	 */
	@Override
	public String toString() {
		return this.getId() + " : " + this.firstName + ", " + this.lastName;
	}

	public boolean isAChild() {
		Calendar dob = this.getBornDate();
		Calendar now = new GregorianCalendar();

		int year1 = now.get(Calendar.YEAR);
		int year2 = dob.get(Calendar.YEAR);
		int age = year1 - year2;
		
		int month1 = now.get(Calendar.MONTH);
		int month2 = dob.get(Calendar.MONTH);

		if (month2 > month1) {
		  age--;
		} else if (month1 == month2) {
		  int day1 = now.get(Calendar.DAY_OF_MONTH);
		  int day2 = dob.get(Calendar.DAY_OF_MONTH);
		  if (day2 > day1) {
		    age--;
		  }
		}

		if(age < Constraints.AGE_OF_CHILD)
			return true;
		else
			return false;
	}
	
	public static int seniority(Calendar date){	
		Calendar now = new GregorianCalendar();

		int year1 = now.get(Calendar.YEAR);
		int year2 = date.get(Calendar.YEAR);
		int age = year1 - year2;
		
		int month1 = now.get(Calendar.MONTH);
		int month2 = date.get(Calendar.MONTH);

		if (month2 > month1) {
		  age--;
		} else if (month1 == month2) {
		  int day1 = now.get(Calendar.DAY_OF_MONTH);
		  int day2 = date.get(Calendar.DAY_OF_MONTH);
		  if (day2 > day1) {
		    age--;
		  }
		}

		return age;
	}
		
	
	public boolean isOneYearSubscriber () {
		return Subscriber.seniority(this.getSubscriptionDate()) <= 1;	
	}
	
	public boolean isTwoYearSubscriber () {
		return Subscriber.seniority(this.getSubscriptionDate()) == 2;	
	}

	public boolean isSupThreeYearSubscriber () {
		return Subscriber.seniority(this.getSubscriptionDate()) >= 3;	
	}
	
	public boolean canlendDvd () {
		if (this.isAChild())
			return false;
		
		if(this.isOneYearSubscriber()){
			return this.getNumberOfDvdLent() < Constraints.maxLOANS_OneYear_DVD;
		}	
		
		if(isTwoYearSubscriber()){
			return this.getNumberOfDvdLent() < Constraints.maxLOANS_TwoYear_DVD;
		}
		
		if(isSupThreeYearSubscriber()){
			return this.getNumberOfDvdLent() < Constraints.maxLOANS_supThreeYear_DVD;
		}
		
		return false;
	}
	

	public boolean canlendBook (){
		if(this.isAChild()) {
			return this.getNumberOfBookLent() < Constraints.maxLOANS_CHILD;
		}

		if(this.isOneYearSubscriber()){
			return this.getNumberOfBookLent() < Constraints.maxLOANS_OneYear_Book;
		}	
		
		if(this.isTwoYearSubscriber()){
			return this.getNumberOfBookLent() < Constraints.maxLOANS_TwoYear_Book;
		}		
		
		if(this.isSupThreeYearSubscriber()){
			return this.getNumberOfBookLent() < Constraints.maxLOANS_supThreeYear_Book;
		}

		return false;
	}

	public boolean canLend(Media media) throws BadParametersException{
		if (media == null)
			throw new BadParametersException();
				
		if(media instanceof Dvd)
			return this.canlendDvd();
		
		if(media instanceof Book)
			return this.canlendBook();
		
		return false;
	
	}

	public boolean canBook(Media media){
		if(media instanceof Dvd && this.isAChild())
			return false;

		if(this.getNumberOfMediaBooked() >= Constraints.maxBOOKING)
			return false;
		else
			return true;
	}
}
